package edu.washington.cs.workflow.analysis.guistructure;

import java.io.File;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import edu.washington.cs.workflow.recommender.EventTraceAnalyzer;
import edu.washington.cs.workflow.util.Files;
import edu.washington.cs.workflow.util.Globals;
import edu.washington.cs.workflow.util.Utils;

public class UIActionTracer {

	public static UIActionTracer tracer = new UIActionTracer();
	
	public static boolean dumpfile = true;
	
	private boolean turnOn = true;
	private boolean realTimeDisplay = true;
	
	private boolean realTimeLogging = false;
	private String dump_file = "trace_file.txt"; //the default file
	private boolean incr_dump = false;
	private List<EventEntity> list = new LinkedList<EventEntity>();
	

	//XXX just for debugging purpose, should be removed
	public boolean delete = false;
	
	static {
		//add a hook to jvm
		Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
            	if(!dumpfile) {
            		Utils.debugPrintln("Not dumping traces to file...");
            		return;
            	}
            	Utils.debugPrintln("----- start dumping traces to file ----");
            	Utils.debugPrintln("Dump traces to file: " + new File("trace_file.txt").getAbsolutePath());
            	//avoid concurrent modification
            	try {
            		int time = 10000;
            		Utils.debugPrintln("Sleep: " + time + " millis before dumping.");
					Thread.sleep(time);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
            	
				tracer.dumpCoveredMethodsToFile();
            	tracer.dumpTracesToFile();
            	
//            	Utils.debugPrintln("----- start analyzing event handlers ----");
//
//            	EventTraceAnalyzer analyzer = new EventTraceAnalyzer(tracer);
//            	analyzer.analyzeEventHandlers();
            }
        });
	}
	
	public void deleteTraceFile() {
		Utils.debugPrintln("Delete the trace file: " + new File(this.dump_file).getAbsolutePath());
		Files.deleteFile(this.dump_file);
	}
	
	public void setTraceFile(String fileName) {
		Utils.checkNotNull(fileName);
		this.dump_file = fileName;
	}
	
	public void setRealtimeLogging(boolean realtime) {
		this.realTimeLogging = realtime;
		//disable dump file option
		if(realtime) {
		    UIActionTracer.dumpfile = false;
		    Utils.debugPrintln("Setting file: " + new File(this.dump_file).getAbsolutePath());
		    //delete the file first
		    Files.deleteFile(this.dump_file);
		}
	}
	
	/**
	 * Trace which action has been performed
	 * */
	public void traceAction(String actionDesc) {
		if(turnOn) {
			UIActionEntity e = new UIActionEntity(actionDesc);
		    list.add(e);
		    if(realTimeDisplay) {
		    	Utils.debugPrintln(Thread.currentThread().getId() + " : " + e);
		    }
		    if(realTimeLogging) {
		    	Files.appendToFileNoExp(e.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
		    }
		    if(incr_dump) {
		    	this.dumpCurrentList();
		    }
		}
	}
	
	/**
	 * Trace which event handler has been invoked
	 * */
	public void traceEventHandlerEntry(String methodDesc, Object obj) {
		if(turnOn) {
			Utils.debugPrintln(Thread.currentThread().getId());
			UIActionHandler h = new UIActionHandler(methodDesc, obj, true); 
			list.add(h);
			if(realTimeDisplay) {
		    	Utils.debugPrintln(Thread.currentThread().getId() + " : " + h);
		    }
			if(realTimeLogging) {
				Files.appendToFileNoExp(h.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
			}
		}
	}
	
	public void traceEventHandlerExit(String methodDesc, Object obj) {
		if(turnOn) {
			UIActionHandler h = new UIActionHandler(methodDesc, obj, false); 
			list.add(h);
			if(realTimeDisplay) {
		    	Utils.debugPrintln(Thread.currentThread().getId() + " : " + h);
		    }
			if(realTimeLogging) {
				Files.appendToFileNoExp(h.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
			}
		}
	}
	
	/**
	 * Trace other non-lib method invocations
	 * */
	public void traceAppMethodEntry(String methodDesc, Object obj) {
        if(turnOn) {
			UIMethodEntity me = new UIMethodEntity(methodDesc, obj, true);
			list.add(me);
			if(realTimeDisplay) {
				Utils.debugPrintln(Thread.currentThread().getId() + " : " + me + ", size: " + this.list.size());
			}
			if(realTimeLogging) {
				Files.appendToFileNoExp(me.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
			}
		}
	}
	
	public void traceAppMethodExit(String methodDesc, Object obj) {
		if(turnOn) {
			UIMethodEntity me = new UIMethodEntity(methodDesc, obj, false);
			list.add(me);
			if(realTimeDisplay) {
				Utils.debugPrintln(Thread.currentThread().getId() + " : " + me);
//				if(this.incr_dump) {
//				    this.dumpCurrentList();
//				}
			}
			if(realTimeLogging) {
				Files.appendToFileNoExp(me.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
			}
		}
	}
	
	private void dumpCurrentList() {
		//dump every 1000 records
		if(list.size() > 1000) { 
		    Utils.debugPrintln("dump current list to: " + new File(this.dump_file).getAbsolutePath());
		    StringBuilder sb = new StringBuilder();
	        for(EventEntity l : list) {
	    	    sb.append(l);
	    	    sb.append(Globals.lineSep);
	        }
	        Files.appendToFileNoExp(sb.toString(), this.dump_file);
	        list.clear();
		}
	}
	
	public void setTurnOn(boolean turnOn) {
		this.turnOn = turnOn;
	}
	
	public void setIncrDump(boolean incrDump) {
		this.incr_dump = incrDump;
		if(incrDump) {
		    UIActionTracer.dumpfile = false;
	        Utils.debugPrintln("Setting file: " + new File(this.dump_file).getAbsolutePath());
	        //delete the file first
	        Files.deleteFile(this.dump_file);
	        Files.createIfNotExistNoExp(this.dump_file);
		}
	}
	
	public void setDisplayRealtime(boolean realtime) {
		this.realTimeDisplay = realtime;
	}
	
	public void dumpTracesToFile() {
		if(turnOn) {
			Utils.debugPrintln("Start to dump traces to file: " + new File(this.dump_file).getAbsolutePath());
			//delete the file first
			Files.deleteFile(this.dump_file);
//			StringBuilder sb = new StringBuilder();
		    for(EventEntity l : list) {
		    	Files.appendToFileNoExp(l.toString(), this.dump_file);
		    	Files.appendToFileNoExp(Globals.lineSep, this.dump_file);
//		    	sb.append(l);
//		    	sb.append(Globals.lineSep);
		    }
		    
//		    if(this.incr_dump) {
//		    	Utils.debugPrintln("Use incremental updates.");
//		    	Files.appendToFileNoExp(sb.toString(), this.dump_file);
//		    } else {
//		        Files.writeToFileNoExp(sb.toString(), this.dump_file);
//		    }
		}
	}
	
	private static String dir = null;
	private static String project = null;
	public static void setDirAndProject(String dir, String project) {
		Utils.checkNotNull(dir);
		Utils.checkNotNull(project);
		UIActionTracer.dir = dir;
		UIActionTracer.project = project;
	}
	public void dumpCoveredMethodsToFile() {
		if(dir == null || project == null) {
			System.out.println("Should set both dir and project variables first.");
			return;
		}

		String methodPath = dir + Globals.fileSep + project + "_covered_methods.txt";
		String handlerPath = dir + Globals.fileSep + project + "_covered_handlers.txt";
		
		Set<String> methodSet = new LinkedHashSet<String>();
		Set<String> handlerSet = new LinkedHashSet<String>();
		for(EventEntity e : list) {
			if(e instanceof UIActionHandler) {
				UIActionHandler h = (UIActionHandler)e;
				methodSet.add(h.methodDesc);
				handlerSet.add(h.methodDesc);
			}
			if(e instanceof UIMethodEntity) {
				UIMethodEntity m = (UIMethodEntity)e;
				methodSet.add(m.methodDesc);
			}
		}
		
		StringBuilder methodSb = new StringBuilder();
		StringBuilder handlerSb = new StringBuilder();
		for(String m : methodSet) {
			methodSb.append(m);
			methodSb.append(Globals.lineSep);
		}
		for(String h : handlerSet) {
			handlerSb.append(h);
			handlerSb.append(Globals.lineSep);
		}
		
		Files.writeToFileNoExp(methodSb.toString(), methodPath);
		Files.writeToFileNoExp(handlerSb.toString(), handlerPath);
	}
	
	public List<EventEntity> getAllUIEntities() {
		return this.list;
	}
	
	
}